Area Data II
NOTE: You can download the source files for this book from here. The source files are in the format of R Notebooks. Notebooks are pretty neat, because the allow you execute code within the notebook, so that you can work interactively with the notes.
In last chapter and activity, you learned about area data and practiced some visualization techniques for spatial data of this type, specifically choropleth maps and cartograms. You also thought about rules to decide whether a mapped variable displayed a spatially random distribution of values.
If you wish to work interactively with this chapter you will need the following:
Learning Objectives
In this practice, you will learn about:
- The concept of proximity for area data.
- How to formalize the concept of proximity: spatial weights matrices.
- How to create spatial weights matrices in R.
- The use of spatial moving averages.
- Other criteria for coding proximity.
Suggested Readings
- Bailey TC and Gatrell AC [-@Bailey1995] Interactive Spatial Data Analysis, Chapter 7. Longman: Essex.
- Bivand RS, Pebesma E, and Gomez-Rubio V [-@Bivand2008] Applied Spatial Data Analysis with R, Chapter 9. Springer: New York.
- Brunsdon C and Comber L [-@Brunsdon2015R] An Introduction to R for Spatial Analysis and Mapping, Chapter 7. Sage: Los Angeles.
- O’Sullivan D and Unwin D [-@Osullivan2010] Geographic Information Analysis, 2nd Edition, Chapter 7. John Wiley & Sons: New Jersey.
Preliminaries
As usual, it is good practice to clear the working space to make sure that you do not have extraneous items there when you begin your work. The command in R to clear the workspace is rm (for “remove”), followed by a list of items to be removed. To clear the workspace from all objects, do the following:
rm(list = ls())
Note that ls() lists all objects currently on the worspace.
Load the libraries you will use in this activity:
library(tidyverse)
library(sf)
library(plotly)
library(spdep)
library(geog4ga3)
Read the data to be used in this chapter. The data is an object of class sf (simple feature) with the census tracts of Hamilton CMA in Canada, and a selection of demographic variables:
data(Hamilton_CT)
You can quickly verify the contents of the dataframe by means of summary:
summary(Hamilton_CT)
ID AREA TRACT POPULATION POP_DENSITY
Min. : 919807 Min. : 0.3154 Length:188 Min. : 5 Min. : 2.591
1st Qu.: 927964 1st Qu.: 0.8552 Class :character 1st Qu.: 2639 1st Qu.: 1438.007
Median : 948130 Median : 1.4157 Mode :character Median : 3595 Median : 2689.737
Mean : 948710 Mean : 7.4578 Mean : 3835 Mean : 2853.078
3rd Qu.: 959722 3rd Qu.: 2.7775 3rd Qu.: 4692 3rd Qu.: 3783.889
Max. :1115750 Max. :138.4466 Max. :11675 Max. :14234.286
AGE_LESS_20 AGE_20_TO_24 AGE_25_TO_29 AGE_30_TO_34 AGE_35_TO_39
Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 0.0
1st Qu.: 528.8 1st Qu.:168.8 1st Qu.:135.0 1st Qu.: 135.0 1st Qu.: 145.0
Median : 750.0 Median :225.0 Median :215.0 Median : 195.0 Median : 200.0
Mean : 899.3 Mean :253.9 Mean :232.8 Mean : 228.2 Mean : 239.6
3rd Qu.:1110.0 3rd Qu.:311.2 3rd Qu.:296.2 3rd Qu.: 281.2 3rd Qu.: 280.0
Max. :3285.0 Max. :835.0 Max. :915.0 Max. :1320.0 Max. :1200.0
AGE_40_TO_44 AGE_45_TO_49 AGE_50_TO_54 AGE_55_TO_59 AGE_60_TO_64 AGE_65_TO_69
Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 0 Min. : 0.0
1st Qu.: 170.0 1st Qu.:203.8 1st Qu.:203.8 1st Qu.:175.0 1st Qu.:140 1st Qu.:115.0
Median : 230.0 Median :282.5 Median :280.0 Median :240.0 Median :220 Median :157.5
Mean : 268.7 Mean :310.6 Mean :300.3 Mean :257.7 Mean :229 Mean :174.2
3rd Qu.: 325.0 3rd Qu.:385.0 3rd Qu.:375.0 3rd Qu.:325.0 3rd Qu.:295 3rd Qu.:221.2
Max. :1105.0 Max. :880.0 Max. :740.0 Max. :625.0 Max. :540 Max. :625.0
AGE_70_TO_74 AGE_75_TO_79 AGE_80_TO_84 AGE_MORE_85 geometry
Min. : 0.0 Min. : 0.00 Min. : 0.00 Min. : 0.00 POLYGON :188
1st Qu.: 90.0 1st Qu.: 68.75 1st Qu.: 50.00 1st Qu.: 35.00 epsg:26917 : 0
Median :130.0 Median :100.00 Median : 77.50 Median : 70.00 +proj=utm ...: 0
Mean :139.7 Mean :118.32 Mean : 95.05 Mean : 87.71
3rd Qu.:180.0 3rd Qu.:160.00 3rd Qu.:120.00 3rd Qu.:105.00
Max. :540.0 Max. :575.00 Max. :420.00 Max. :400.00
Proximity in Area Data
In the earlier part of the text, when working with point data, the spatial relationships among events (their proximity) were more or less unambiguously given by their relative location, or more precisely by their distance. Hence, we had quadrat-based techniques (relative location with respect to a grid) and distance-based techniques (event-to-event and point-to-event).
In the case of area data, spatial proximity can be represented in more ways, given the characteristics of areas. In particular, an area contains an infinite number of points, and measuring distance between two areas leads to many possible results, depending on which pairs of points within two zones are used to measure the distance.
Consider the simple systems shown in Figure @ref{fig:simple-zoning-system}. Which of zones \(A_2\), \(A_3\), and \(A_4\) is more proximate to \(A_1\)?

If points are selected in such a way that they are on the overlapping edges of two contiguous areas, the distance between the two areas clearly is zero, and they must be proximate.
This criterion to define proximity is called adjacency. Adjacency means that two zones share a common edge. This is conventionally called the rook criterion, after chess, in which the piece called the rook can move only orthogonally. The rook criterion, however, would dictate that zones \(A_2\) and \(A_6\) are not proximate, despite being closer than \(A_2\) and \(A_3\).
When this criterion is expanded to allow contact at a single point between zones (say, the corner between \(A_2\) and \(A_6\)), the adjacency criterion is called queen, again, for the chess piece that moves both orthogonally and diagonally.
If we accept adjacency as a reasonable way of expressing relationships of proximity between areas, what we need is a way of coding relationships of adjacency in a way that is convenient and amenable to manipulation for data analysis.
One of the most widely used tools to code proximity in area data is the spatial weights matrix.
Spatial Weights Matrices
A spatial weights matrix is an arrangement of values (or weights) for all pairs of zones in a system. For instance, in a zoning system such as shown in Figure 1, with 6 zones, there will be \(6 \times 6\) such weights. The weights are organized by rows, in such a way that each zone has a corresponding row of weights. For example, zone \(A_1\) in Figure 1 has weights: \[
w_{1\cdot} = [w_{11}, w_{12}, w_{13}, w_{14}, w_{15}, w_{16}]
\]
The values of the weights depend on the adjacency criterion adopted. The simplest coding scheme is when we assign a value of 1 to pairs of zones that are adjacent, and a value of 0 to pairs of zones that are not.
Lets formalize the two criteria mentioned above:
\[
w_{ij}=\bigg\{\begin{array}{l l}
1\text{ if } A_i \text{ and } A_j \text{ share an edge}\\
0\text{ otherwise}\\
\end{array}
\]
\[
w_{ij}=\bigg\{\begin{array}{l l}
1\text{ if } A_i \text{ and } A_j \text{ share an edge or a vertex}\\
0\text{ otherwise}\\
\end{array}
\]
If queen adjacency is used, the weights for zone \(A_6\) are as follows: \[
w_{6\cdot} = [0, 1, 0, 1, 1, 0].
\]
As you can see, the adjacent areas from the perspective of \(A_6\) are \(A_4\) and \(A_5\) (by virtue of sharing an edge), and \(A_2\) (by virtue of sharing a vertex). These three areas receive weights of 1. On the other hand, \(A_1\) and \(A_3\) are not adjacent, and therefore receive a weight of zero. Notice how the weight \(w_{66}\) is set to zero. By convention, an area is not its own neighbor!
The set of weights above define the neighborhood of \(A_6\).
The spatial weights matrix for the whole system in Figure 1 is as follows: \[
\textbf{W}=\left (\begin{array}{c c c c c c}
0 & 1 & 1 & 1 & 0 & 0\\
1 & 0 & 0 & 1 & 1 & 1\\
1 & 0 & 0 & 1 & 0 & 0\\
1 & 1 & 1 & 0 & 1 & 1\\
0 & 1 & 0 & 1 & 0 & 1\\
0 & 1 & 0 & 1 & 1 & 0\\
\end{array} \right).
\]
Compare the matrix to the zoning system. The spatial weights matrix has the following properties:
- The main diagonal elements are all zeros (no area is its own neighbor).
- Each zone has a row of weights in the matrix: row number one corresponds to \(A_1\), row number two corresponds to \(A_2\), and so on.
- Likewise, each zone has a column of weights.
- The sum of all values in a row gives the total number of neighbors for an area. That is: \[
\text{The total number of neighbors of } A_i \text{ is given by: }\sum_{j=1}^{n}{w_{ij}}
\]
The spatial weights matrix is often processed to obtain a row-standardized spatial weights matrix. This procedure consists of dividing all weights by the sum of their corresponding row (i.e., by the total number of neighbors), as follows: \[
w_{ij}^{st}=\frac{w_{ij}}{\sum_{j=1}^n{w_{ij}}}
\]
The row-standardized weights matrix for the system in Figure 1 is: \[
\textbf{W}^{st}=\left (\begin{array}{c c c c c c}
0 & 1/3 & 1/3 & 1/3 & 0 & 0\\
1/4 & 0 & 0 & 1/4 & 1/4 & 1/4\\
1/2 & 0 & 0 & 1/2 & 0 & 0\\
1/5 & 1/5 & 1/5 & 0 & 1/5 & 1/5\\
0 & 1/3 & 0 & 1/3 & 0 & 1/3\\
0 & 1/3 & 0 & 1/3 & 1/3 & 0\\
\end{array} \right).
\]
The row-standardized spatial weights matrix has the following properties:
Each weight now represents the proportion of a neighbor out of the total of neighbors. For instance, since the total of neighbors of \(A_1\) is 3, each neighbor contributes 1/3 to that total.
The sum of all weights over a row equals 1, or 100% of all neighbors for that zone.
Creating Spatial Weights Matrices in R
Coding spatial weights matrices by hand is a tedious and error-prone process. Fortunately, functions to generate them exist in R. The package spdep, for instance, has a number of useful utilities for working with spatial weights matrices.
The first step to create a spatial weights matrix is to find the neighbors (i.e., areas adjacent to) for each area. The function poly2nb is used for this. The input argument is a SpatialPolygonDataFrame. This means that our sf object needs to be converted into a SpatialPolygonDataFrame:
Hamilton_CT.sp <- as(Hamilton_CT, "Spatial")
The following finds the neighbors (note that the default adjacency criterion is queen):
Hamilton_CT.nb <- poly2nb(pl = Hamilton_CT.sp, queen = TRUE)
The value (output) of the function is an object of class nb:
class(Hamilton_CT.nb)
[1] "nb"
The function summary applied to an object of this class gives some useful information about the neighbors, including the number of areas in this system (188), the total number of neighbors (1180), and the percentage of neighbors out of all pairs of areas (3.34%). Other information includes the distribution of neighbors (3 zones have two neighbors, 8 zones have three neighbors, 22 zones have four neighbors, and so on):
summary(Hamilton_CT.nb)
Neighbour list object:
Number of regions: 188
Number of nonzero links: 1180
Percentage nonzero weights: 3.338615
Average number of links: 6.276596
Link number distribution:
2 3 4 5 6 7 8 9 10 11 12 14
3 8 22 32 35 45 30 6 1 1 4 1
3 least connected regions:
174 175 188 with 2 links
1 most connected region:
33 with 14 links
The nb object is a list that contains the neighbors for each area. For instance, the neighbors of census tract 5370001.01 (the first tract in the dataframe) are the following tracts:
Hamilton_CT$TRACT[Hamilton_CT.nb[[1]]]
[1] "5370120.02" "5370122.01" "5370122.02" "5370124.00" "5370142.01" "5370133.01" "5370130.03"
The list of neighbors can be converted into a list of entries in a spatial weights matrix W by means of the function nb2list2 (for “neighbors to W in list form”):
Hamilton_CT.w <- nb2listw(Hamilton_CT.nb)
We can visualize the neighbors (adjacent) areas:
plot(Hamilton_CT.sp, border = "gray")
plot(Hamilton_CT.nb, coordinates(Hamilton_CT.sp), col = "red", add = TRUE)

Spatial Moving Averages
The spatial weights matrix, and in particular its row-standardized version, is useful to calculate a spatial statistic, the spatial moving average.
The spatial moving average is a variation on the mean statistic. Recall that the mean is calculated as the sum of all relevant values divided by the number of values summed. In the case of spatial data, the mean is what we would call a global statistics, since it is calculated using all data for a region: \[
\overline{x}=\frac{1}{n}\sum_{j=1}^{n}{x_j}
\] where \(\overline{x}\) (read x-bar) is the mean of all values of x.
A spatial moving average is calculated in the same way, but for each area, and based only on the values of proximate areas: \[
\overline{x_i}=\frac{1}{n_i}\sum_{j\in N(i)}{x_j}
\] where \(n_i\) is the number of neighbors of \(A_i\), and the sum is only for \(x_j\) that are in the neighborhood of i (\(j\in N(i)\) is read “j in the neighborhood of i”).
Lets illustrate this by making reference again to Figure 1.
Consider \(A_1\). The spatial weights matrix indicates that the neighborhood of \(A_1\) consists of three areas: \(A_2\), \(A_3\), and \(A_4\). Therefore \(n_1=3\), and \(j\in N(1)\) are 2, 3, and 4.
The spatial moving average of \(A_1\) for a variable \(x\) would then be calculated as: \[
\overline{x_1}=\frac{x_2 + x_3 + x_4}{3}
\]
Notice that another way of writing the spatial moving average expression is as follows, since membership in the neighborhood of \(i\) is implicit in the definition of \(w_{ij}\)! Since \(w_{ij}\) takes values of zero and one, the effect is to turn on and off the values of \(x\) depending on whether they are for areas adjacent to \(i\): \[
\overline{x_i}=\frac{1}{n_i}\sum_{j=1}^n{w_{ij}x_j}
\]
This means that the spatial moving average of \(A_1\) for a variable \(x\) on this system can also be calculated using the spatial weights matrix as: \[
\overline{x_1}=\frac{w_{11}x_1 + w_{12}x_2 + w_{13}x_3 + w_{14}x_4 + w_{15}x_5 + w_{12}x_6}{3}
\]
Substituting the spatial weights: \[
\overline{x_1}=\frac{0x_1 + 1x_2 + 1x_3 + 1x_4 + 0x_5 + 0x_6}{3} = \frac{x_2 + x_3 + x_4}{3}
\]
In other words, the spatial weights can be used directly in the calculation of spatial moving averages.
Further, notice that: \[
n_i=\sum_{j=1}^{n}w_{ij}
\] which is simply the total number of neighbors of \(A_i\), and the value we used to row-standardize the spatial weights.
Since the row-standardized weights have already been divided by the number of neighbors, we can use them to express the spatial moving average as follows: \[
\overline{x_i}=\sum_{j=1}^{n}{w_{ij}^{st}x_j}
\]
Continuing the example, if we use the row-standardized weights, the spatial moving average at \(A_1\) is: \[
\overline{x_i}=0x_1 + \frac{1}{3}x_2 + \frac{1}{3}x_3 + \frac{1}{3}x_4 + 0x_5 + 0x_6
\] which is the same as: \[
\overline{x_i}=\frac{x_2 + x_3 + x_4}{3}
\]
Consider the following map of Hamilton’s population density:
map <- ggplot(data = Hamilton_CT) +
geom_sf(aes(fill = cut_number(Hamilton_CT$POP_DENSITY, 5),
POP_DENSITY = round(POP_DENSITY),
TRACT = TRACT),
color = "black") +
geom_sf(data = subset(Hamilton_CT, TRACT == "5370142.02"),
aes(POP_DENSITY = round(POP_DENSITY),
TRACT = TRACT),
color = "red",
weight = 3, fill = NA) +
geom_sf(data = subset(Hamilton_CT, TRACT == "5370144.01"),
aes(POP_DENSITY = round(POP_DENSITY),
TRACT = TRACT),
color = "green",
weight = 3, fill = NA) +
scale_fill_brewer(palette = "YlOrRd") +
labs(fill = "Pop Density") +
coord_sf()
ggplotly(map, tooltip = c("TRACT", "POP_DENSIT"))
Manually calculate the spatial moving average for tract 5370142.02 (with the red boundary) and tract (with the green boundary). Tip: hover over the tracts to see their population densities.
(32 + 109 + 48)/3
[1] 63
(48 + 55 + 125)/3
[1] 76
Spatial moving averages can be calculated in a straighforward way by means of the function lag.listw function of the spdep package. This function uses a spatial weights matrix and automatically selects the row-standardized weights.
Lets calculate the spatial moving average of population density:
POP_DENSITY.sma <- lag.listw(x = Hamilton_CT.w, Hamilton_CT$POP_DENSITY)
Lets now plot the spatial moving average of population density. First we add this variable to our tidy dataframe:
Hamilton_CT <- left_join(Hamilton_CT, data.frame(TRACT = Hamilton_CT$TRACT, POP_DENSITY.sma))
Joining, by = "TRACT"
Column `TRACT` joining character vector and factor, coercing into character vector
And plot:
map.sma <- ggplot() +
geom_sf(data = Hamilton_CT,
aes(fill = cut_number(Hamilton_CT$POP_DENSITY.sma, 5),
POP_DENSITY.sma = round(POP_DENSITY.sma),
TRACT = TRACT),
color = "black") +
geom_sf(data = subset(Hamilton_CT, TRACT == "5370142.02"),
aes(POP_DENSITY.sma = round(POP_DENSITY.sma),
TRACT = TRACT),
color = "red",
weight = 3, fill = NA) +
geom_sf(data = subset(Hamilton_CT, TRACT == "5370144.01"),
aes(POP_DENSITY.sma = round(POP_DENSITY.sma),
TRACT = TRACT),
color = "green",
weight = 3, fill = NA) +
scale_fill_brewer(palette = "YlOrRd") +
labs(fill = "Pop Density SMA") +
coord_sf()
ggplotly(map.sma, tooltip = c("TRACT", "POP_DENSIT.sma"))
Verify that your manual calculations for the two tracts above are correct. What differences do you notice between the map of population density and the map of spatial moving averages of population density?
Other Criteria for Coding Proximity
Adjacenty is not the only criterion for coding proximity.
Occasionally, the distance between areas is calculated by using the centroids of the areas as their representative points. A centroid is simply the mean of the coordinates of the edges of an area, and in this way represent the “centre of gravity” of the area.
The inter-centroid distance allows us to define additional criteria for proximity, including neighbors within a certain distance threshold, and k-nearest neighbors.
\[
w_{ij}=\bigg\{\begin{array}{l l}
1\text{ if inter-centroid distance } d_{ij}\leq \delta\\
0\text{ otherwise}\\
\end{array}
\] where \(\delta\) is a distance threshold.
Distance-based nearest neighbors can be obtained in R by means of the function dnearneigh.
First we need to obtain the coordinates of the centroids of the areas. These are the first two columns of the output of st_coordinates function:
CT_centroids <- coordinates(Hamilton_CT.sp)
A nearest neighbors object nb is produced as follows (selecting a distance threshold between 0 and 5 km):
Hamilton_CT.dnb <- dnearneigh(CT_centroids, d1 = 0, d2 = 5000)
We can visualize the neighbors (adjacent) areas:
plot(Hamilton_CT.sp, border = "gray")
plot(Hamilton_CT.dnb, CT_centroids, col = "red", add = TRUE)

Try changing the distance threshold to see how different neighborhoods are defined.
\[
w_{ij}=\bigg\{\begin{array}{l l}
1\text{ if } A_j \text{ is one of k-nearest neighbors of } A_i\\
0\text{ otherwise}\\
\end{array}
\]
A potential disadvantage of using a distance-based criterion is that for zoning systems with areas of vastly different sizes, small areas will end up having many neighbors, whereas large areas will have few or none.
The criterion of \(k\)-nearest neighbors allows for some adaptation to the size of the areas. Under this criterion, all areas have the exact same number of neighbors, but the geographical extent of the neighborhood may (and likely will) change.
In R, \(k\)-nearest neighbors can be obtained by means of the function knearneigh, and the arguments include the value of \(k\):
Hamilton_CT.knb <- knn2nb(knearneigh(CT_centroids, k = 3))
We can again visualize the neighbors (“adjacent”) areas:
plot(Hamilton_CT.sp, border = "gray")
plot(Hamilton_CT.knb, CT_centroids, col = "red", add = TRUE)

Try changing the value of k to see how the neighborhoods change.
LS0tDQp0aXRsZTogIjExIEFyZWEgRGF0YSBJSSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgQXJlYSBEYXRhIElJDQoNCipOT1RFKjogWW91IGNhbiBkb3dubG9hZCB0aGUgc291cmNlIGZpbGVzIGZvciB0aGlzIGJvb2sgZnJvbSBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL3BhZXpoYS9TcGF0aWFsLVN0YXRpc3RpY3MtQ291cnNlKS4gVGhlIHNvdXJjZSBmaWxlcyBhcmUgaW4gdGhlIGZvcm1hdCBvZiBSIE5vdGVib29rcy4gTm90ZWJvb2tzIGFyZSBwcmV0dHkgbmVhdCwgYmVjYXVzZSB0aGUgYWxsb3cgeW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCBzbyB0aGF0IHlvdSBjYW4gd29yayBpbnRlcmFjdGl2ZWx5IHdpdGggdGhlIG5vdGVzLg0KDQpJbiBsYXN0IGNoYXB0ZXIgYW5kIGFjdGl2aXR5LCB5b3UgbGVhcm5lZCBhYm91dCBfYXJlYSBkYXRhXyBhbmQgcHJhY3RpY2VkIHNvbWUgdmlzdWFsaXphdGlvbiB0ZWNobmlxdWVzIGZvciBzcGF0aWFsIGRhdGEgb2YgdGhpcyB0eXBlLCBzcGVjaWZpY2FsbHkgY2hvcm9wbGV0aCBtYXBzIGFuZCBjYXJ0b2dyYW1zLiBZb3UgYWxzbyB0aG91Z2h0IGFib3V0IHJ1bGVzIHRvIGRlY2lkZSB3aGV0aGVyIGEgbWFwcGVkIHZhcmlhYmxlIGRpc3BsYXllZCBhIHNwYXRpYWxseSByYW5kb20gZGlzdHJpYnV0aW9uIG9mIHZhbHVlcy4gDQoNCklmIHlvdSB3aXNoIHRvIHdvcmsgaW50ZXJhY3RpdmVseSB3aXRoIHRoaXMgY2hhcHRlciB5b3Ugd2lsbCBuZWVkIHRoZSBmb2xsb3dpbmc6DQoNCiogQW4gUiBtYXJrZG93biBub3RlYm9vayB2ZXJzaW9uIG9mIHRoaXMgZG9jdW1lbnQgKHRoZSBzb3VyY2UgZmlsZSkuDQoNCiogQSBwYWNrYWdlIGNhbGxlZCBgZ2VvZzRnYTNgLg0KDQojIyBMZWFybmluZyBPYmplY3RpdmVzDQoNCkluIHRoaXMgcHJhY3RpY2UsIHlvdSB3aWxsIGxlYXJuIGFib3V0Og0KDQoxLiBUaGUgY29uY2VwdCBvZiBwcm94aW1pdHkgZm9yIGFyZWEgZGF0YS4NCjIuIEhvdyB0byBmb3JtYWxpemUgdGhlIGNvbmNlcHQgb2YgcHJveGltaXR5OiBzcGF0aWFsIHdlaWdodHMgbWF0cmljZXMuDQozLiBIb3cgdG8gY3JlYXRlIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaWNlcyBpbiBSLg0KNC4gVGhlIHVzZSBvZiBzcGF0aWFsIG1vdmluZyBhdmVyYWdlcy4NCjUuIE90aGVyIGNyaXRlcmlhIGZvciBjb2RpbmcgcHJveGltaXR5Lg0KDQojIyBTdWdnZXN0ZWQgUmVhZGluZ3MNCg0KLSBCYWlsZXkgVEMgYW5kIEdhdHJlbGwgQUMgWy1AQmFpbGV5MTk5NV0gSW50ZXJhY3RpdmUgU3BhdGlhbCBEYXRhIEFuYWx5c2lzLCBDaGFwdGVyIDcuIExvbmdtYW46IEVzc2V4Lg0KLSBCaXZhbmQgUlMsIFBlYmVzbWEgRSwgYW5kIEdvbWV6LVJ1YmlvIFYgWy1AQml2YW5kMjAwOF0gQXBwbGllZCBTcGF0aWFsIERhdGEgQW5hbHlzaXMgd2l0aCBSLCBDaGFwdGVyIDkuIFNwcmluZ2VyOiBOZXcgWW9yay4NCi0gQnJ1bnNkb24gQyBhbmQgQ29tYmVyIEwgWy1AQnJ1bnNkb24yMDE1Ul0gQW4gSW50cm9kdWN0aW9uIHRvIFIgZm9yIFNwYXRpYWwgQW5hbHlzaXMgYW5kIE1hcHBpbmcsIENoYXB0ZXIgNy4gU2FnZTogTG9zIEFuZ2VsZXMuDQotIE8nU3VsbGl2YW4gRCBhbmQgVW53aW4gRCBbLUBPc3VsbGl2YW4yMDEwXSBHZW9ncmFwaGljIEluZm9ybWF0aW9uIEFuYWx5c2lzLCAybmQgRWRpdGlvbiwgQ2hhcHRlciA3LiBKb2huIFdpbGV5ICYgU29uczogTmV3IEplcnNleS4NCg0KIyMgUHJlbGltaW5hcmllcw0KDQpBcyB1c3VhbCwgaXQgaXMgZ29vZCBwcmFjdGljZSB0byBjbGVhciB0aGUgd29ya2luZyBzcGFjZSB0byBtYWtlIHN1cmUgdGhhdCB5b3UgZG8gbm90IGhhdmUgZXh0cmFuZW91cyBpdGVtcyB0aGVyZSB3aGVuIHlvdSBiZWdpbiB5b3VyIHdvcmsuIFRoZSBjb21tYW5kIGluIFIgdG8gY2xlYXIgdGhlIHdvcmtzcGFjZSBpcyBgcm1gIChmb3IgInJlbW92ZSIpLCBmb2xsb3dlZCBieSBhIGxpc3Qgb2YgaXRlbXMgdG8gYmUgcmVtb3ZlZC4gVG8gY2xlYXIgdGhlIHdvcmtzcGFjZSBmcm9tIF9hbGxfIG9iamVjdHMsIGRvIHRoZSBmb2xsb3dpbmc6DQpgYGB7cn0NCnJtKGxpc3QgPSBscygpKQ0KYGBgDQoNCk5vdGUgdGhhdCBgbHMoKWAgbGlzdHMgYWxsIG9iamVjdHMgY3VycmVudGx5IG9uIHRoZSB3b3JzcGFjZS4NCg0KTG9hZCB0aGUgbGlicmFyaWVzIHlvdSB3aWxsIHVzZSBpbiB0aGlzIGFjdGl2aXR5Og0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzZikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShzcGRlcCkNCmxpYnJhcnkoZ2VvZzRnYTMpDQpgYGANCg0KUmVhZCB0aGUgZGF0YSB0byBiZSB1c2VkIGluIHRoaXMgY2hhcHRlci4gVGhlIGRhdGEgaXMgYW4gb2JqZWN0IG9mIGNsYXNzIGBzZmAgKHNpbXBsZSBmZWF0dXJlKSB3aXRoIHRoZSBjZW5zdXMgdHJhY3RzIG9mIEhhbWlsdG9uIENNQSBpbiBDYW5hZGEsIGFuZCBhIHNlbGVjdGlvbiBvZiBkZW1vZ3JhcGhpYyB2YXJpYWJsZXM6DQpgYGB7cn0NCmRhdGEoSGFtaWx0b25fQ1QpDQpgYGANCg0KWW91IGNhbiBxdWlja2x5IHZlcmlmeSB0aGUgY29udGVudHMgb2YgdGhlIGRhdGFmcmFtZSBieSBtZWFucyBvZiBgc3VtbWFyeWA6DQpgYGB7cn0NCnN1bW1hcnkoSGFtaWx0b25fQ1QpDQpgYGANCg0KIyMgUHJveGltaXR5IGluIEFyZWEgRGF0YQ0KDQpJbiB0aGUgZWFybGllciBwYXJ0IG9mIHRoZSB0ZXh0LCB3aGVuIHdvcmtpbmcgd2l0aCBwb2ludCBkYXRhLCB0aGUgc3BhdGlhbCByZWxhdGlvbnNoaXBzIGFtb25nIGV2ZW50cyAodGhlaXIgcHJveGltaXR5KSB3ZXJlIG1vcmUgb3IgbGVzcyB1bmFtYmlndW91c2x5IGdpdmVuIGJ5IHRoZWlyIHJlbGF0aXZlIGxvY2F0aW9uLCBvciBtb3JlIHByZWNpc2VseSBieSB0aGVpciBkaXN0YW5jZS4gSGVuY2UsIHdlIGhhZCBxdWFkcmF0LWJhc2VkIHRlY2huaXF1ZXMgKHJlbGF0aXZlIGxvY2F0aW9uIHdpdGggcmVzcGVjdCB0byBhIGdyaWQpIGFuZCBkaXN0YW5jZS1iYXNlZCB0ZWNobmlxdWVzIChldmVudC10by1ldmVudCBhbmQgcG9pbnQtdG8tZXZlbnQpLg0KDQpJbiB0aGUgY2FzZSBvZiBhcmVhIGRhdGEsIHNwYXRpYWwgcHJveGltaXR5IGNhbiBiZSByZXByZXNlbnRlZCBpbiBtb3JlIHdheXMsIGdpdmVuIHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgYXJlYXMuIEluIHBhcnRpY3VsYXIsIGFuIGFyZWEgY29udGFpbnMgYW4gaW5maW5pdGUgbnVtYmVyIG9mIHBvaW50cywgYW5kIG1lYXN1cmluZyBkaXN0YW5jZSBiZXR3ZWVuIHR3byBhcmVhcyBsZWFkcyB0byBtYW55IHBvc3NpYmxlIHJlc3VsdHMsIGRlcGVuZGluZyBvbiB3aGljaCBwYWlycyBvZiBwb2ludHMgd2l0aGluIHR3byB6b25lcyBhcmUgdXNlZCB0byBtZWFzdXJlIHRoZSBkaXN0YW5jZS4gDQoNCkNvbnNpZGVyIHRoZSBzaW1wbGUgc3lzdGVtcyBzaG93biBpbiBGaWd1cmUgXEByZWZ7ZmlnOnNpbXBsZS16b25pbmctc3lzdGVtfS4gV2hpY2ggb2Ygem9uZXMgJEFfMiQsICRBXzMkLCBhbmQgJEFfNCQgaXMgbW9yZSBfcHJveGltYXRlXyB0byAkQV8xJD8NCg0KYGBge3Igc2ltcGxlLXpvbmluZy1zeXN0ZW0sIGZpZy5jYXA9ICJcXGxhYmVse2ZpZzpzaW1wbGUtem9uaW5nLXN5c3RlbX1TaW1wbGUgem9uaW5nIHN5c3RlbSIsIGVjaG89RkFMU0V9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhyZXAoIkFyZWEtRGF0YS1JSS1GaWd1cmUtMS5qcGciKSkNCmBgYA0KDQpJZiBwb2ludHMgYXJlIHNlbGVjdGVkIGluIHN1Y2ggYSB3YXkgdGhhdCB0aGV5IGFyZSBvbiB0aGUgb3ZlcmxhcHBpbmcgZWRnZXMgb2YgdHdvIGNvbnRpZ3VvdXMgYXJlYXMsIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gYXJlYXMgY2xlYXJseSBpcyB6ZXJvLCBhbmQgdGhleSBtdXN0IGJlIHByb3hpbWF0ZS4NCg0KVGhpcyBjcml0ZXJpb24gdG8gZGVmaW5lIHByb3hpbWl0eSBpcyBjYWxsZWQgX2FkamFjZW5jeV8uIEFkamFjZW5jeSBtZWFucyB0aGF0IHR3byB6b25lcyBzaGFyZSBhIGNvbW1vbiBlZGdlLiBUaGlzIGlzIGNvbnZlbnRpb25hbGx5IGNhbGxlZCB0aGUgX3Jvb2tfIGNyaXRlcmlvbiwgYWZ0ZXIgY2hlc3MsIGluIHdoaWNoIHRoZSBwaWVjZSBjYWxsZWQgdGhlIHJvb2sgY2FuIG1vdmUgb25seSBvcnRob2dvbmFsbHkuIFRoZSByb29rIGNyaXRlcmlvbiwgaG93ZXZlciwgd291bGQgZGljdGF0ZSB0aGF0IHpvbmVzICRBXzIkIGFuZCAkQV82JCBhcmUgbm90IHByb3hpbWF0ZSwgZGVzcGl0ZSBiZWluZyBjbG9zZXIgdGhhbiAkQV8yJCBhbmQgJEFfMyQuDQoNCldoZW4gdGhpcyBjcml0ZXJpb24gaXMgZXhwYW5kZWQgdG8gYWxsb3cgY29udGFjdCBhdCBhIHNpbmdsZSBwb2ludCBiZXR3ZWVuIHpvbmVzIChzYXksIHRoZSBjb3JuZXIgYmV0d2VlbiAkQV8yJCBhbmQgJEFfNiQpLCB0aGUgYWRqYWNlbmN5IGNyaXRlcmlvbiBpcyBjYWxsZWQgX3F1ZWVuXywgYWdhaW4sIGZvciB0aGUgY2hlc3MgcGllY2UgdGhhdCBtb3ZlcyBib3RoIG9ydGhvZ29uYWxseSBhbmQgZGlhZ29uYWxseS4NCg0KSWYgd2UgYWNjZXB0IGFkamFjZW5jeSBhcyBhIHJlYXNvbmFibGUgd2F5IG9mIGV4cHJlc3NpbmcgcmVsYXRpb25zaGlwcyBvZiBwcm94aW1pdHkgYmV0d2VlbiBhcmVhcywgd2hhdCB3ZSBuZWVkIGlzIGEgd2F5IG9mIGNvZGluZyByZWxhdGlvbnNoaXBzIG9mIGFkamFjZW5jeSBpbiBhIHdheSB0aGF0IGlzIGNvbnZlbmllbnQgYW5kIGFtZW5hYmxlIHRvIG1hbmlwdWxhdGlvbiBmb3IgZGF0YSBhbmFseXNpcy4NCg0KT25lIG9mIHRoZSBtb3N0IHdpZGVseSB1c2VkIHRvb2xzIHRvIGNvZGUgcHJveGltaXR5IGluIGFyZWEgZGF0YSBpcyB0aGUgX3NwYXRpYWwgd2VpZ2h0cyBtYXRyaXhfLg0KDQojIyBTcGF0aWFsIFdlaWdodHMgTWF0cmljZXMNCg0KQSBzcGF0aWFsIHdlaWdodHMgbWF0cml4IGlzIGFuIGFycmFuZ2VtZW50IG9mIHZhbHVlcyAob3Igd2VpZ2h0cykgZm9yIGFsbCBwYWlycyBvZiB6b25lcyBpbiBhIHN5c3RlbS4gRm9yIGluc3RhbmNlLCBpbiBhIHpvbmluZyBzeXN0ZW0gc3VjaCBhcyBzaG93biBpbiBGaWd1cmUgMSwgd2l0aCA2IHpvbmVzLCB0aGVyZSB3aWxsIGJlICQ2IFx0aW1lcyA2JCBzdWNoIHdlaWdodHMuIFRoZSB3ZWlnaHRzIGFyZSBvcmdhbml6ZWQgYnkgcm93cywgaW4gc3VjaCBhIHdheSB0aGF0IGVhY2ggem9uZSBoYXMgYSBjb3JyZXNwb25kaW5nIHJvdyBvZiB3ZWlnaHRzLiBGb3IgZXhhbXBsZSwgem9uZSAkQV8xJCBpbiBGaWd1cmUgMSBoYXMgd2VpZ2h0czoNCiQkDQp3X3sxXGNkb3R9ID0gW3dfezExfSwgd197MTJ9LCB3X3sxM30sIHdfezE0fSwgd197MTV9LCB3X3sxNn1dDQokJA0KDQpUaGUgdmFsdWVzIG9mIHRoZSB3ZWlnaHRzIGRlcGVuZCBvbiB0aGUgYWRqYWNlbmN5IGNyaXRlcmlvbiBhZG9wdGVkLiBUaGUgc2ltcGxlc3QgY29kaW5nIHNjaGVtZSBpcyB3aGVuIHdlIGFzc2lnbiBhIHZhbHVlIG9mIDEgdG8gcGFpcnMgb2Ygem9uZXMgdGhhdCBhcmUgYWRqYWNlbnQsIGFuZCBhIHZhbHVlIG9mIDAgdG8gcGFpcnMgb2Ygem9uZXMgdGhhdCBhcmUgbm90LiANCg0KTGV0cyBmb3JtYWxpemUgdGhlIHR3byBjcml0ZXJpYSBtZW50aW9uZWQgYWJvdmU6DQoNCiogUm9vayBjcml0ZXJpb24NCg0KJCQNCndfe2lqfT1cYmlnZ1x7XGJlZ2lue2FycmF5fXtsIGx9DQoxXHRleHR7IGlmIH0gQV9pIFx0ZXh0eyBhbmQgfSBBX2ogXHRleHR7IHNoYXJlIGFuIGVkZ2V9XFwNCjBcdGV4dHsgb3RoZXJ3aXNlfVxcDQpcZW5ke2FycmF5fQ0KJCQNCg0KKiBRdWVlbiBjcml0ZXJpb24NCg0KJCQNCndfe2lqfT1cYmlnZ1x7XGJlZ2lue2FycmF5fXtsIGx9DQoxXHRleHR7IGlmIH0gQV9pIFx0ZXh0eyBhbmQgfSBBX2ogXHRleHR7IHNoYXJlIGFuIGVkZ2Ugb3IgYSB2ZXJ0ZXh9XFwNCjBcdGV4dHsgb3RoZXJ3aXNlfVxcDQpcZW5ke2FycmF5fQ0KJCQNCg0KSWYgcXVlZW4gYWRqYWNlbmN5IGlzIHVzZWQsIHRoZSB3ZWlnaHRzIGZvciB6b25lICRBXzYkIGFyZSBhcyBmb2xsb3dzOg0KJCQNCndfezZcY2RvdH0gPSBbMCwgMSwgMCwgMSwgMSwgMF0uDQokJA0KDQpBcyB5b3UgY2FuIHNlZSwgdGhlIGFkamFjZW50IGFyZWFzIGZyb20gdGhlIHBlcnNwZWN0aXZlIG9mICRBXzYkIGFyZSAkQV80JCBhbmQgJEFfNSQgKGJ5IHZpcnR1ZSBvZiBzaGFyaW5nIGFuIGVkZ2UpLCBhbmQgJEFfMiQgKGJ5IHZpcnR1ZSBvZiBzaGFyaW5nIGEgdmVydGV4KS4gVGhlc2UgdGhyZWUgYXJlYXMgcmVjZWl2ZSB3ZWlnaHRzIG9mIDEuIE9uIHRoZSBvdGhlciBoYW5kLCAkQV8xJCBhbmQgJEFfMyQgYXJlIG5vdCBhZGphY2VudCwgYW5kIHRoZXJlZm9yZSByZWNlaXZlIGEgd2VpZ2h0IG9mIHplcm8uICoqTm90aWNlIGhvdyB0aGUgd2VpZ2h0ICR3X3s2Nn0kIGlzIHNldCB0byB6ZXJvKiouIEJ5IGNvbnZlbnRpb24sIGFuIGFyZWEgaXMgbm90IGl0cyBvd24gbmVpZ2hib3IhDQoNClRoZSBzZXQgb2Ygd2VpZ2h0cyBhYm92ZSBkZWZpbmUgdGhlIF9uZWlnaGJvcmhvb2RfIG9mICRBXzYkLg0KDQpUaGUgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeCBmb3IgdGhlIHdob2xlIHN5c3RlbSBpbiBGaWd1cmUgMSBpcyBhcyBmb2xsb3dzOg0KJCQNClx0ZXh0YmZ7V309XGxlZnQgKFxiZWdpbnthcnJheX17YyBjIGMgYyBjIGN9DQowICYgMSAmIDEgJiAxICYgMCAmIDBcXA0KMSAmIDAgJiAwICYgMSAmIDEgJiAxXFwNCjEgJiAwICYgMCAmIDEgJiAwICYgMFxcDQoxICYgMSAmIDEgJiAwICYgMSAmIDFcXA0KMCAmIDEgJiAwICYgMSAmIDAgJiAxXFwNCjAgJiAxICYgMCAmIDEgJiAxICYgMFxcDQpcZW5ke2FycmF5fSBccmlnaHQpLg0KJCQNCg0KQ29tcGFyZSB0aGUgbWF0cml4IHRvIHRoZSB6b25pbmcgc3lzdGVtLiBUaGUgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeCBoYXMgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOg0KDQoxLiBUaGUgbWFpbiBkaWFnb25hbCBlbGVtZW50cyBhcmUgYWxsIHplcm9zIChubyBhcmVhIGlzIGl0cyBvd24gbmVpZ2hib3IpLg0KMi4gRWFjaCB6b25lIGhhcyBhIHJvdyBvZiB3ZWlnaHRzIGluIHRoZSBtYXRyaXg6IHJvdyBudW1iZXIgb25lIGNvcnJlc3BvbmRzIHRvICRBXzEkLCByb3cgbnVtYmVyIHR3byBjb3JyZXNwb25kcyB0byAkQV8yJCwgYW5kIHNvIG9uLg0KMy4gTGlrZXdpc2UsIGVhY2ggem9uZSBoYXMgYSBfY29sdW1uXyBvZiB3ZWlnaHRzLg0KNC4gVGhlIHN1bSBvZiBhbGwgdmFsdWVzIGluIGEgcm93IGdpdmVzIHRoZSBfdG90YWxfIG51bWJlciBvZiBuZWlnaGJvcnMgZm9yIGFuIGFyZWEuIFRoYXQgaXM6DQokJA0KXHRleHR7VGhlIHRvdGFsIG51bWJlciBvZiBuZWlnaGJvcnMgb2YgfSBBX2kgXHRleHR7IGlzIGdpdmVuIGJ5OiB9XHN1bV97aj0xfV57bn17d197aWp9fQ0KJCQNCg0KVGhlIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggaXMgb2Z0ZW4gcHJvY2Vzc2VkIHRvIG9idGFpbiBhIF9yb3ctc3RhbmRhcmRpemVkXyBzcGF0aWFsIHdlaWdodHMgbWF0cml4LiBUaGlzIHByb2NlZHVyZSBjb25zaXN0cyBvZiBkaXZpZGluZyBhbGwgd2VpZ2h0cyBieSB0aGUgc3VtIG9mIHRoZWlyIGNvcnJlc3BvbmRpbmcgcm93IChpLmUuLCBieSB0aGUgdG90YWwgbnVtYmVyIG9mIG5laWdoYm9ycyksIGFzIGZvbGxvd3M6DQokJA0Kd197aWp9XntzdH09XGZyYWN7d197aWp9fXtcc3VtX3tqPTF9Xm57d197aWp9fX0NCiQkDQoNClRoZSByb3ctc3RhbmRhcmRpemVkIHdlaWdodHMgbWF0cml4IGZvciB0aGUgc3lzdGVtIGluIEZpZ3VyZSAxIGlzOg0KJCQNClx0ZXh0YmZ7V31ee3N0fT1cbGVmdCAoXGJlZ2lue2FycmF5fXtjIGMgYyBjIGMgY30NCjAgJiAxLzMgJiAxLzMgJiAxLzMgJiAwICYgMFxcDQoxLzQgJiAwICYgMCAmIDEvNCAmIDEvNCAmIDEvNFxcDQoxLzIgJiAwICYgMCAmIDEvMiAmIDAgJiAwXFwNCjEvNSAmIDEvNSAmIDEvNSAmIDAgJiAxLzUgJiAxLzVcXA0KMCAmIDEvMyAmIDAgJiAxLzMgJiAwICYgMS8zXFwNCjAgJiAxLzMgJiAwICYgMS8zICYgMS8zICYgMFxcDQpcZW5ke2FycmF5fSBccmlnaHQpLg0KJCQNCg0KVGhlIHJvdy1zdGFuZGFyZGl6ZWQgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeCBoYXMgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOg0KDQoxLiBFYWNoIHdlaWdodCBub3cgcmVwcmVzZW50cyB0aGUgcHJvcG9ydGlvbiBvZiBhIG5laWdoYm9yIG91dCBvZiB0aGUgdG90YWwgb2YgbmVpZ2hib3JzLiBGb3IgaW5zdGFuY2UsIHNpbmNlIHRoZSB0b3RhbCBvZiBuZWlnaGJvcnMgb2YgJEFfMSQgaXMgMywgZWFjaCBuZWlnaGJvciBjb250cmlidXRlcyAxLzMgdG8gdGhhdCB0b3RhbC4NCg0KMi4gVGhlIHN1bSBvZiBhbGwgd2VpZ2h0cyBvdmVyIGEgcm93IGVxdWFscyAxLCBvciAxMDAlIG9mIGFsbCBuZWlnaGJvcnMgZm9yIHRoYXQgem9uZS4NCg0KIyMgQ3JlYXRpbmcgU3BhdGlhbCBXZWlnaHRzIE1hdHJpY2VzIGluIFINCg0KQ29kaW5nIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaWNlcyBieSBoYW5kIGlzIGEgdGVkaW91cyBhbmQgZXJyb3ItcHJvbmUgcHJvY2Vzcy4gRm9ydHVuYXRlbHksIGZ1bmN0aW9ucyB0byBnZW5lcmF0ZSB0aGVtIGV4aXN0IGluIFIuIFRoZSBwYWNrYWdlIGBzcGRlcGAsIGZvciBpbnN0YW5jZSwgaGFzIGEgbnVtYmVyIG9mIHVzZWZ1bCB1dGlsaXRpZXMgZm9yIHdvcmtpbmcgd2l0aCBzcGF0aWFsIHdlaWdodHMgbWF0cmljZXMuDQoNClRoZSBmaXJzdCBzdGVwIHRvIGNyZWF0ZSBhIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggaXMgdG8gZmluZCB0aGUgbmVpZ2hib3JzIChpLmUuLCBhcmVhcyBhZGphY2VudCB0bykgZm9yIGVhY2ggYXJlYS4gVGhlIGZ1bmN0aW9uIGBwb2x5Mm5iYCBpcyB1c2VkIGZvciB0aGlzLiBUaGUgaW5wdXQgYXJndW1lbnQgaXMgYSBgU3BhdGlhbFBvbHlnb25EYXRhRnJhbWVgLiBUaGlzIG1lYW5zIHRoYXQgb3VyIGBzZmAgb2JqZWN0IG5lZWRzIHRvIGJlIGNvbnZlcnRlZCBpbnRvIGEgYFNwYXRpYWxQb2x5Z29uRGF0YUZyYW1lYDoNCmBgYHtyfQ0KSGFtaWx0b25fQ1Quc3AgPC0gYXMoSGFtaWx0b25fQ1QsICJTcGF0aWFsIikNCmBgYA0KDQpUaGUgZm9sbG93aW5nIGZpbmRzIHRoZSBuZWlnaGJvcnMgKG5vdGUgdGhhdCB0aGUgZGVmYXVsdCBhZGphY2VuY3kgY3JpdGVyaW9uIGlzIHF1ZWVuKToNCmBgYHtyfQ0KSGFtaWx0b25fQ1QubmIgPC0gcG9seTJuYihwbCA9IEhhbWlsdG9uX0NULnNwLCBxdWVlbiA9IFRSVUUpDQpgYGANCg0KVGhlIHZhbHVlIChvdXRwdXQpIG9mIHRoZSBmdW5jdGlvbiBpcyBhbiBvYmplY3Qgb2YgY2xhc3MgYG5iYDoNCmBgYHtyfQ0KY2xhc3MoSGFtaWx0b25fQ1QubmIpDQpgYGANCg0KVGhlIGZ1bmN0aW9uIGBzdW1tYXJ5YCBhcHBsaWVkIHRvIGFuIG9iamVjdCBvZiB0aGlzIGNsYXNzIGdpdmVzIHNvbWUgdXNlZnVsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBuZWlnaGJvcnMsIGluY2x1ZGluZyB0aGUgbnVtYmVyIG9mIGFyZWFzIGluIHRoaXMgc3lzdGVtICgxODgpLCB0aGUgdG90YWwgbnVtYmVyIG9mIG5laWdoYm9ycyAoMTE4MCksIGFuZCB0aGUgcGVyY2VudGFnZSBvZiBuZWlnaGJvcnMgb3V0IG9mIGFsbCBwYWlycyBvZiBhcmVhcyAoMy4zNCUpLiBPdGhlciBpbmZvcm1hdGlvbiBpbmNsdWRlcyB0aGUgZGlzdHJpYnV0aW9uIG9mIG5laWdoYm9ycyAoMyB6b25lcyBoYXZlIHR3byBuZWlnaGJvcnMsIDggem9uZXMgaGF2ZSB0aHJlZSBuZWlnaGJvcnMsIDIyIHpvbmVzIGhhdmUgZm91ciBuZWlnaGJvcnMsIGFuZCBzbyBvbik6IA0KYGBge3J9DQpzdW1tYXJ5KEhhbWlsdG9uX0NULm5iKQ0KYGBgDQoNClRoZSBgbmJgIG9iamVjdCBpcyBhIGxpc3QgdGhhdCBjb250YWlucyB0aGUgbmVpZ2hib3JzIGZvciBlYWNoIGFyZWEuIEZvciBpbnN0YW5jZSwgdGhlIG5laWdoYm9ycyBvZiBjZW5zdXMgdHJhY3QgNTM3MDAwMS4wMSAodGhlIGZpcnN0IHRyYWN0IGluIHRoZSBkYXRhZnJhbWUpIGFyZSB0aGUgZm9sbG93aW5nIHRyYWN0czoNCmBgYHtyfQ0KSGFtaWx0b25fQ1QkVFJBQ1RbSGFtaWx0b25fQ1QubmJbWzFdXV0NCmBgYA0KDQpUaGUgbGlzdCBvZiBuZWlnaGJvcnMgY2FuIGJlIGNvbnZlcnRlZCBpbnRvIGEgbGlzdCBvZiBlbnRyaWVzIGluIGEgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeCBXIGJ5IG1lYW5zIG9mIHRoZSBmdW5jdGlvbiBgbmIybGlzdDJgIChmb3IgIm5laWdoYm9ycyB0byBXIGluIGxpc3QgZm9ybSIpOg0KYGBge3J9DQpIYW1pbHRvbl9DVC53IDwtIG5iMmxpc3R3KEhhbWlsdG9uX0NULm5iKQ0KYGBgDQoNCldlIGNhbiB2aXN1YWxpemUgdGhlIG5laWdoYm9ycyAoYWRqYWNlbnQpIGFyZWFzOg0KYGBge3J9DQpwbG90KEhhbWlsdG9uX0NULnNwLCBib3JkZXIgPSAiZ3JheSIpDQpwbG90KEhhbWlsdG9uX0NULm5iLCBjb29yZGluYXRlcyhIYW1pbHRvbl9DVC5zcCksIGNvbCA9ICJyZWQiLCBhZGQgPSBUUlVFKQ0KYGBgDQoNCiMjIFNwYXRpYWwgTW92aW5nIEF2ZXJhZ2VzDQoNClRoZSBzcGF0aWFsIHdlaWdodHMgbWF0cml4LCBhbmQgaW4gcGFydGljdWxhciBpdHMgcm93LXN0YW5kYXJkaXplZCB2ZXJzaW9uLCBpcyB1c2VmdWwgdG8gY2FsY3VsYXRlIGEgc3BhdGlhbCBzdGF0aXN0aWMsIHRoZSBfc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZV8uDQoNClRoZSBzcGF0aWFsIG1vdmluZyBhdmVyYWdlIGlzIGEgdmFyaWF0aW9uIG9uIHRoZSBtZWFuIHN0YXRpc3RpYy4gUmVjYWxsIHRoYXQgdGhlIG1lYW4gaXMgY2FsY3VsYXRlZCBhcyB0aGUgc3VtIG9mIGFsbCByZWxldmFudCB2YWx1ZXMgZGl2aWRlZCBieSB0aGUgbnVtYmVyIG9mIHZhbHVlcyBzdW1tZWQuIEluIHRoZSBjYXNlIG9mIHNwYXRpYWwgZGF0YSwgdGhlIG1lYW4gaXMgd2hhdCB3ZSB3b3VsZCBjYWxsIGEgX2dsb2JhbF8gc3RhdGlzdGljcywgc2luY2UgaXQgaXMgY2FsY3VsYXRlZCB1c2luZyBhbGwgZGF0YSBmb3IgYSByZWdpb246DQokJA0KXG92ZXJsaW5le3h9PVxmcmFjezF9e259XHN1bV97aj0xfV57bn17eF9qfQ0KJCQNCndoZXJlICRcb3ZlcmxpbmV7eH0kIChyZWFkIHgtYmFyKSBpcyB0aGUgbWVhbiBvZiBhbGwgdmFsdWVzIG9mIHguDQoNCkEgc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZSBpcyBjYWxjdWxhdGVkIGluIHRoZSBzYW1lIHdheSwgYnV0IGZvciBlYWNoIGFyZWEsIGFuZCBiYXNlZCBvbmx5IG9uIHRoZSB2YWx1ZXMgb2YgcHJveGltYXRlIGFyZWFzOg0KJCQNClxvdmVybGluZXt4X2l9PVxmcmFjezF9e25faX1cc3VtX3tqXGluIE4oaSl9e3hfan0NCiQkDQp3aGVyZSAkbl9pJCBpcyB0aGUgbnVtYmVyIG9mIG5laWdoYm9ycyBvZiAkQV9pJCwgYW5kIHRoZSBzdW0gaXMgb25seSBmb3IgJHhfaiQgdGhhdCBhcmUgaW4gdGhlIG5laWdoYm9yaG9vZCBvZiBpICgkalxpbiBOKGkpJCBpcyByZWFkICJqIGluIHRoZSBuZWlnaGJvcmhvb2Qgb2YgaSIpLg0KDQpMZXRzIGlsbHVzdHJhdGUgdGhpcyBieSBtYWtpbmcgcmVmZXJlbmNlIGFnYWluIHRvIEZpZ3VyZSAxLg0KDQpDb25zaWRlciAkQV8xJC4gVGhlIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggaW5kaWNhdGVzIHRoYXQgdGhlIG5laWdoYm9yaG9vZCBvZiAkQV8xJCBjb25zaXN0cyBvZiB0aHJlZSBhcmVhczogJEFfMiQsICRBXzMkLCBhbmQgJEFfNCQuIFRoZXJlZm9yZSAkbl8xPTMkLCBhbmQgJGpcaW4gTigxKSQgYXJlIDIsIDMsIGFuZCA0Lg0KDQpUaGUgc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZSBvZiAkQV8xJCBmb3IgYSB2YXJpYWJsZSAkeCQgd291bGQgdGhlbiBiZSBjYWxjdWxhdGVkIGFzOg0KJCQNClxvdmVybGluZXt4XzF9PVxmcmFje3hfMiArIHhfMyArIHhfNH17M30NCiQkDQoNCk5vdGljZSB0aGF0IGFub3RoZXIgd2F5IG9mIHdyaXRpbmcgdGhlIHNwYXRpYWwgbW92aW5nIGF2ZXJhZ2UgZXhwcmVzc2lvbiBpcyBhcyBmb2xsb3dzLCBzaW5jZSBtZW1iZXJzaGlwIGluIHRoZSBuZWlnaGJvcmhvb2Qgb2YgJGkkIGlzIGltcGxpY2l0IGluIHRoZSBkZWZpbml0aW9uIG9mICR3X3tpan0kISBTaW5jZSAkd197aWp9JCB0YWtlcyB2YWx1ZXMgb2YgemVybyBhbmQgb25lLCB0aGUgZWZmZWN0IGlzIHRvIHR1cm4gb24gYW5kIG9mZiB0aGUgdmFsdWVzIG9mICR4JCBkZXBlbmRpbmcgb24gd2hldGhlciB0aGV5IGFyZSBmb3IgYXJlYXMgYWRqYWNlbnQgdG8gJGkkOg0KJCQNClxvdmVybGluZXt4X2l9PVxmcmFjezF9e25faX1cc3VtX3tqPTF9Xm57d197aWp9eF9qfQ0KJCQNCg0KVGhpcyBtZWFucyB0aGF0IHRoZSBzcGF0aWFsIG1vdmluZyBhdmVyYWdlIG9mICRBXzEkIGZvciBhIHZhcmlhYmxlICR4JCBvbiB0aGlzIHN5c3RlbSBjYW4gYWxzbyBiZSBjYWxjdWxhdGVkIHVzaW5nIHRoZSBzcGF0aWFsIHdlaWdodHMgbWF0cml4IGFzOg0KJCQNClxvdmVybGluZXt4XzF9PVxmcmFje3dfezExfXhfMSArIHdfezEyfXhfMiArIHdfezEzfXhfMyArIHdfezE0fXhfNCArIHdfezE1fXhfNSArIHdfezEyfXhfNn17M30NCiQkDQoNClN1YnN0aXR1dGluZyB0aGUgc3BhdGlhbCB3ZWlnaHRzOg0KJCQNClxvdmVybGluZXt4XzF9PVxmcmFjezB4XzEgKyAxeF8yICsgMXhfMyArIDF4XzQgKyAweF81ICsgMHhfNn17M30gPSBcZnJhY3t4XzIgKyB4XzMgKyB4XzR9ezN9DQokJA0KDQpJbiBvdGhlciB3b3JkcywgdGhlIHNwYXRpYWwgd2VpZ2h0cyBjYW4gYmUgdXNlZCBkaXJlY3RseSBpbiB0aGUgY2FsY3VsYXRpb24gb2Ygc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZXMuDQoNCkZ1cnRoZXIsIG5vdGljZSB0aGF0Og0KJCQNCm5faT1cc3VtX3tqPTF9XntufXdfe2lqfQ0KJCQNCndoaWNoIGlzIHNpbXBseSB0aGUgdG90YWwgbnVtYmVyIG9mIG5laWdoYm9ycyBvZiAkQV9pJCwgYW5kIHRoZSB2YWx1ZSB3ZSB1c2VkIHRvIHJvdy1zdGFuZGFyZGl6ZSB0aGUgc3BhdGlhbCB3ZWlnaHRzLg0KDQpTaW5jZSB0aGUgcm93LXN0YW5kYXJkaXplZCB3ZWlnaHRzIGhhdmUgYWxyZWFkeSBiZWVuIGRpdmlkZWQgYnkgdGhlIG51bWJlciBvZiBuZWlnaGJvcnMsIHdlIGNhbiB1c2UgdGhlbSB0byBleHByZXNzIHRoZSBzcGF0aWFsIG1vdmluZyBhdmVyYWdlIGFzIGZvbGxvd3M6DQokJA0KXG92ZXJsaW5le3hfaX09XHN1bV97aj0xfV57bn17d197aWp9XntzdH14X2p9DQokJA0KDQpDb250aW51aW5nIHRoZSBleGFtcGxlLCBpZiB3ZSB1c2UgdGhlIHJvdy1zdGFuZGFyZGl6ZWQgd2VpZ2h0cywgdGhlIHNwYXRpYWwgbW92aW5nIGF2ZXJhZ2UgYXQgJEFfMSQgaXM6DQokJA0KXG92ZXJsaW5le3hfaX09MHhfMSArIFxmcmFjezF9ezN9eF8yICsgXGZyYWN7MX17M314XzMgKyBcZnJhY3sxfXszfXhfNCArIDB4XzUgKyAweF82DQokJA0Kd2hpY2ggaXMgdGhlIHNhbWUgYXM6DQokJA0KXG92ZXJsaW5le3hfaX09XGZyYWN7eF8yICsgeF8zICsgeF80fXszfQ0KJCQNCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBtYXAgb2YgSGFtaWx0b24ncyBwb3B1bGF0aW9uIGRlbnNpdHk6DQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KbWFwIDwtIGdncGxvdChkYXRhID0gSGFtaWx0b25fQ1QpICsNCiAgZ2VvbV9zZihhZXMoZmlsbCA9IGN1dF9udW1iZXIoSGFtaWx0b25fQ1QkUE9QX0RFTlNJVFksIDUpLCANCiAgICAgICAgICAgICAgICAgICBQT1BfREVOU0lUWSA9IHJvdW5kKFBPUF9ERU5TSVRZKSwNCiAgICAgICAgICAgICAgICAgICBUUkFDVCA9IFRSQUNUKSwgDQogICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsNCiAgZ2VvbV9zZihkYXRhID0gc3Vic2V0KEhhbWlsdG9uX0NULCBUUkFDVCA9PSAiNTM3MDE0Mi4wMiIpLCANCiAgICAgICAgICAgICAgIGFlcyhQT1BfREVOU0lUWSA9IHJvdW5kKFBPUF9ERU5TSVRZKSwNCiAgICAgICAgICAgICAgICAgICBUUkFDVCA9IFRSQUNUKSwgDQogICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLA0KICAgICAgICAgICAgICAgd2VpZ2h0ID0gMywgZmlsbCA9IE5BKSArDQogIGdlb21fc2YoZGF0YSA9IHN1YnNldChIYW1pbHRvbl9DVCwgVFJBQ1QgPT0gIjUzNzAxNDQuMDEiKSwgDQogICAgICAgICAgICAgICBhZXMoUE9QX0RFTlNJVFkgPSByb3VuZChQT1BfREVOU0lUWSksDQogICAgICAgICAgICAgICAgICAgVFJBQ1QgPSBUUkFDVCksIA0KICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JlZW4iLA0KICAgICAgICAgICAgICAgd2VpZ2h0ID0gMywgZmlsbCA9IE5BKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiWWxPclJkIikgKw0KICBsYWJzKGZpbGwgPSAiUG9wIERlbnNpdHkiKSArDQogIGNvb3JkX3NmKCkNCmdncGxvdGx5KG1hcCwgdG9vbHRpcCA9IGMoIlRSQUNUIiwgIlBPUF9ERU5TSVQiKSkNCmBgYA0KDQpNYW51YWxseSBjYWxjdWxhdGUgdGhlIHNwYXRpYWwgbW92aW5nIGF2ZXJhZ2UgZm9yIHRyYWN0IDUzNzAxNDIuMDIgKHdpdGggdGhlIHJlZCBib3VuZGFyeSkgYW5kIHRyYWN0ICh3aXRoIHRoZSBncmVlbiBib3VuZGFyeSkuICoqVGlwKio6IGhvdmVyIG92ZXIgdGhlIHRyYWN0cyB0byBzZWUgdGhlaXIgcG9wdWxhdGlvbiBkZW5zaXRpZXMuDQoNCmBgYHtyfQ0KKDMyICsgMTA5ICsgNDgpLzMNCig0OCArIDU1ICsgMTI1KS8zDQpgYGANCg0KU3BhdGlhbCBtb3ZpbmcgYXZlcmFnZXMgY2FuIGJlIGNhbGN1bGF0ZWQgaW4gYSBzdHJhaWdoZm9yd2FyZCB3YXkgYnkgbWVhbnMgb2YgdGhlIGZ1bmN0aW9uIGBsYWcubGlzdHdgIGZ1bmN0aW9uIG9mIHRoZSBgc3BkZXBgIHBhY2thZ2UuIFRoaXMgZnVuY3Rpb24gdXNlcyBhIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggYW5kIGF1dG9tYXRpY2FsbHkgc2VsZWN0cyB0aGUgcm93LXN0YW5kYXJkaXplZCB3ZWlnaHRzLg0KDQpMZXRzIGNhbGN1bGF0ZSB0aGUgc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZSBvZiBwb3B1bGF0aW9uIGRlbnNpdHk6DQpgYGB7cn0NClBPUF9ERU5TSVRZLnNtYSA8LSBsYWcubGlzdHcoeCA9IEhhbWlsdG9uX0NULncsIEhhbWlsdG9uX0NUJFBPUF9ERU5TSVRZKQ0KYGBgDQoNCkxldHMgbm93IHBsb3QgdGhlIHNwYXRpYWwgbW92aW5nIGF2ZXJhZ2Ugb2YgcG9wdWxhdGlvbiBkZW5zaXR5LiBGaXJzdCB3ZSBhZGQgdGhpcyB2YXJpYWJsZSB0byBvdXIgdGlkeSBkYXRhZnJhbWU6DQpgYGB7cn0NCkhhbWlsdG9uX0NUIDwtIGxlZnRfam9pbihIYW1pbHRvbl9DVCwgZGF0YS5mcmFtZShUUkFDVCA9IEhhbWlsdG9uX0NUJFRSQUNULCBQT1BfREVOU0lUWS5zbWEpKQ0KYGBgDQoNCkFuZCBwbG90Og0KYGBge3Igd2FybmluZz1GQUxTRX0NCm1hcC5zbWEgPC0gZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBIYW1pbHRvbl9DVCwNCiAgICAgICAgICBhZXMoZmlsbCA9IGN1dF9udW1iZXIoSGFtaWx0b25fQ1QkUE9QX0RFTlNJVFkuc21hLCA1KSwNCiAgICAgICAgICAgICAgUE9QX0RFTlNJVFkuc21hID0gcm91bmQoUE9QX0RFTlNJVFkuc21hKSwNCiAgICAgICAgICAgICAgVFJBQ1QgPSBUUkFDVCksDQogICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArDQogIGdlb21fc2YoZGF0YSA9IHN1YnNldChIYW1pbHRvbl9DVCwgVFJBQ1QgPT0gIjUzNzAxNDIuMDIiKSwgDQogICAgICAgICAgYWVzKFBPUF9ERU5TSVRZLnNtYSA9IHJvdW5kKFBPUF9ERU5TSVRZLnNtYSksDQogICAgICAgICAgICAgIFRSQUNUID0gVFJBQ1QpLCANCiAgICAgICAgICBjb2xvciA9ICJyZWQiLA0KICAgICAgICAgIHdlaWdodCA9IDMsIGZpbGwgPSBOQSkgKw0KICBnZW9tX3NmKGRhdGEgPSBzdWJzZXQoSGFtaWx0b25fQ1QsIFRSQUNUID09ICI1MzcwMTQ0LjAxIiksIA0KICAgICAgICAgIGFlcyhQT1BfREVOU0lUWS5zbWEgPSByb3VuZChQT1BfREVOU0lUWS5zbWEpLA0KICAgICAgICAgICAgICBUUkFDVCA9IFRSQUNUKSwgDQogICAgICAgICAgY29sb3IgPSAiZ3JlZW4iLA0KICAgICAgICAgIHdlaWdodCA9IDMsIGZpbGwgPSBOQSkgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIllsT3JSZCIpICsNCiAgbGFicyhmaWxsID0gIlBvcCBEZW5zaXR5IFNNQSIpICsNCiAgY29vcmRfc2YoKQ0KZ2dwbG90bHkobWFwLnNtYSwgdG9vbHRpcCA9IGMoIlRSQUNUIiwgIlBPUF9ERU5TSVQuc21hIikpDQpgYGANCg0KVmVyaWZ5IHRoYXQgeW91ciBtYW51YWwgY2FsY3VsYXRpb25zIGZvciB0aGUgdHdvIHRyYWN0cyBhYm92ZSBhcmUgY29ycmVjdC4gV2hhdCBkaWZmZXJlbmNlcyBkbyB5b3Ugbm90aWNlIGJldHdlZW4gdGhlIG1hcCBvZiBwb3B1bGF0aW9uIGRlbnNpdHkgYW5kIHRoZSBtYXAgb2Ygc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZXMgb2YgcG9wdWxhdGlvbiBkZW5zaXR5Pw0KDQojIyBPdGhlciBDcml0ZXJpYSBmb3IgQ29kaW5nIFByb3hpbWl0eQ0KDQpBZGphY2VudHkgaXMgbm90IHRoZSBvbmx5IGNyaXRlcmlvbiBmb3IgY29kaW5nIHByb3hpbWl0eS4NCg0KT2NjYXNpb25hbGx5LCB0aGUgZGlzdGFuY2UgYmV0d2VlbiBhcmVhcyBpcyBjYWxjdWxhdGVkIGJ5IHVzaW5nIHRoZSBfY2VudHJvaWRzXyBvZiB0aGUgYXJlYXMgYXMgdGhlaXIgcmVwcmVzZW50YXRpdmUgcG9pbnRzLiBBIGNlbnRyb2lkIGlzIHNpbXBseSB0aGUgbWVhbiBvZiB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIGVkZ2VzIG9mIGFuIGFyZWEsIGFuZCBpbiB0aGlzIHdheSByZXByZXNlbnQgdGhlICJjZW50cmUgb2YgZ3Jhdml0eSIgb2YgdGhlIGFyZWEuDQoNClRoZSBpbnRlci1jZW50cm9pZCBkaXN0YW5jZSBhbGxvd3MgdXMgdG8gZGVmaW5lIGFkZGl0aW9uYWwgY3JpdGVyaWEgZm9yIHByb3hpbWl0eSwgaW5jbHVkaW5nIG5laWdoYm9ycyB3aXRoaW4gYSBjZXJ0YWluIGRpc3RhbmNlIHRocmVzaG9sZCwgYW5kIGstbmVhcmVzdCBuZWlnaGJvcnMuDQoNCiogRGlzdGFuY2UtYmFzZWQgY3JpdGVyaW9uDQoNCiQkDQp3X3tpan09XGJpZ2dce1xiZWdpbnthcnJheX17bCBsfQ0KMVx0ZXh0eyBpZiBpbnRlci1jZW50cm9pZCBkaXN0YW5jZSB9IGRfe2lqfVxsZXEgXGRlbHRhXFwNCjBcdGV4dHsgb3RoZXJ3aXNlfVxcDQpcZW5ke2FycmF5fQ0KJCQNCndoZXJlICRcZGVsdGEkIGlzIGEgZGlzdGFuY2UgdGhyZXNob2xkLg0KDQpEaXN0YW5jZS1iYXNlZCBuZWFyZXN0IG5laWdoYm9ycyBjYW4gYmUgb2J0YWluZWQgaW4gUiBieSBtZWFucyBvZiB0aGUgZnVuY3Rpb24gYGRuZWFybmVpZ2hgLg0KDQpGaXJzdCB3ZSBuZWVkIHRvIG9idGFpbiB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIGNlbnRyb2lkcyBvZiB0aGUgYXJlYXMuIFRoZXNlIGFyZSB0aGUgZmlyc3QgdHdvIGNvbHVtbnMgb2YgdGhlIG91dHB1dCBvZiBgc3RfY29vcmRpbmF0ZXNgIGZ1bmN0aW9uOg0KYGBge3J9DQpDVF9jZW50cm9pZHMgPC0gY29vcmRpbmF0ZXMoSGFtaWx0b25fQ1Quc3ApDQpgYGANCg0KQSBuZWFyZXN0IG5laWdoYm9ycyBvYmplY3QgYG5iYCBpcyBwcm9kdWNlZCBhcyBmb2xsb3dzIChzZWxlY3RpbmcgYSBkaXN0YW5jZSB0aHJlc2hvbGQgYmV0d2VlbiAwIGFuZCA1IGttKToNCmBgYHtyfQ0KSGFtaWx0b25fQ1QuZG5iIDwtIGRuZWFybmVpZ2goQ1RfY2VudHJvaWRzLCBkMSA9IDAsIGQyID0gNTAwMCkNCmBgYA0KDQpXZSBjYW4gdmlzdWFsaXplIHRoZSBuZWlnaGJvcnMgKGFkamFjZW50KSBhcmVhczoNCmBgYHtyfQ0KcGxvdChIYW1pbHRvbl9DVC5zcCwgYm9yZGVyID0gImdyYXkiKQ0KcGxvdChIYW1pbHRvbl9DVC5kbmIsIENUX2NlbnRyb2lkcywgY29sID0gInJlZCIsIGFkZCA9IFRSVUUpDQpgYGANCg0KVHJ5IGNoYW5naW5nIHRoZSBkaXN0YW5jZSB0aHJlc2hvbGQgdG8gc2VlIGhvdyBkaWZmZXJlbnQgbmVpZ2hib3Job29kcyBhcmUgZGVmaW5lZC4NCg0KKiAkayQtbmVhcmVzdCBuZWlnaGJvcnMNCg0KJCQNCndfe2lqfT1cYmlnZ1x7XGJlZ2lue2FycmF5fXtsIGx9DQoxXHRleHR7IGlmIH0gQV9qIFx0ZXh0eyBpcyBvbmUgb2Ygay1uZWFyZXN0IG5laWdoYm9ycyBvZiB9IEFfaVxcDQowXHRleHR7IG90aGVyd2lzZX1cXA0KXGVuZHthcnJheX0NCiQkDQoNCkEgcG90ZW50aWFsIGRpc2FkdmFudGFnZSBvZiB1c2luZyBhIGRpc3RhbmNlLWJhc2VkIGNyaXRlcmlvbiBpcyB0aGF0IGZvciB6b25pbmcgc3lzdGVtcyB3aXRoIGFyZWFzIG9mIHZhc3RseSBkaWZmZXJlbnQgc2l6ZXMsIHNtYWxsIGFyZWFzIHdpbGwgZW5kIHVwIGhhdmluZyBtYW55IG5laWdoYm9ycywgd2hlcmVhcyBsYXJnZSBhcmVhcyB3aWxsIGhhdmUgZmV3IG9yIG5vbmUuDQoNClRoZSBjcml0ZXJpb24gb2YgJGskLW5lYXJlc3QgbmVpZ2hib3JzIGFsbG93cyBmb3Igc29tZSBhZGFwdGF0aW9uIHRvIHRoZSBzaXplIG9mIHRoZSBhcmVhcy4gVW5kZXIgdGhpcyBjcml0ZXJpb24sIGFsbCBhcmVhcyBoYXZlIHRoZSBleGFjdCBzYW1lIG51bWJlciBvZiBuZWlnaGJvcnMsIGJ1dCB0aGUgZ2VvZ3JhcGhpY2FsIGV4dGVudCBvZiB0aGUgbmVpZ2hib3Job29kIG1heSAoYW5kIGxpa2VseSB3aWxsKSBjaGFuZ2UuDQoNCkluIFIsICRrJC1uZWFyZXN0IG5laWdoYm9ycyBjYW4gYmUgb2J0YWluZWQgYnkgbWVhbnMgb2YgdGhlIGZ1bmN0aW9uIGBrbmVhcm5laWdoYCwgYW5kIHRoZSBhcmd1bWVudHMgaW5jbHVkZSB0aGUgdmFsdWUgb2YgJGskOg0KYGBge3J9DQpIYW1pbHRvbl9DVC5rbmIgPC0ga25uMm5iKGtuZWFybmVpZ2goQ1RfY2VudHJvaWRzLCBrID0gMykpDQpgYGANCg0KV2UgY2FuIGFnYWluIHZpc3VhbGl6ZSB0aGUgbmVpZ2hib3JzICgiYWRqYWNlbnQiKSBhcmVhczoNCmBgYHtyfQ0KcGxvdChIYW1pbHRvbl9DVC5zcCwgYm9yZGVyID0gImdyYXkiKQ0KcGxvdChIYW1pbHRvbl9DVC5rbmIsIENUX2NlbnRyb2lkcywgY29sID0gInJlZCIsIGFkZCA9IFRSVUUpDQpgYGANCg0KVHJ5IGNoYW5naW5nIHRoZSB2YWx1ZSBvZiBga2AgdG8gc2VlIGhvdyB0aGUgbmVpZ2hib3Job29kcyBjaGFuZ2Uu